package server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import javax.net.ssl.SSLServerSocket;

import protocol.Info;
import sslsockets.SSLSocketHandler;

public class Server
{
    public static String DNName;
    public static String hashKeyAlias;
    static String keyStoreFile;
    static String trustKeyStoreFile;
    static private char[] keyStorePassword;
    static private char[] trustKeyStorePassword;
    static String shamirPubFile;
    static String shamirPrivFile;
    private KeyStore keyStore;
    private KeyStore trustKeyStore;
    private boolean isRunning = true;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 );

    public Server()
    {
        /*
         * 1. wygeneruj, potnij i przechowaj gdzies Shamira
         */
        // ShamirWorker sw = new ShamirWorker(shamirPrivFile, shamirPubFile);
        // sw.generate();
        /*
         * 2. wygeneruj i wczytaj certifikat PKW
         */
        try
        {
            keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
            trustKeyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
            FileInputStream fis = new FileInputStream( new File( keyStoreFile ) );
            keyStore.load( fis, keyStorePassword );
            fis.close();
            fis = new FileInputStream( trustKeyStoreFile );
            trustKeyStore.load( fis, trustKeyStorePassword );
            // // note to self : pusty keyStore jest ok, wiec juz nie zmieniaj -
            // testowane
            // trustKeyStore.load(null, trustKeyStorePassword);
        }
        catch( Exception e )
        {
            throw new RuntimeException( e );
        }
    }

    static
    {
        ServerResources resources = new ServerResources();
        keyStoreFile = (String)resources.getObject( "keyStoreFile" );
        trustKeyStoreFile = (String)resources.getObject( "trustKeyStoreFile" );
        keyStorePassword = (char[])resources.getObject( "keyStorePassword" );
        trustKeyStorePassword = (char[])resources.getObject( "trustKeyStorePassword" );
        shamirPrivFile = (String)resources.getObject( "shamirPrivFile" );
        shamirPubFile = (String)resources.getObject( "shamirPubFile" );
        DNName = (String)resources.getObject( "PKWDNString" );
        hashKeyAlias = (String)resources.getObject( "hashKeyAlias" );
    }

    /**
     * Saves trustKeyStore every 30 seconds until the end of registration
     */
    private void storeTrustKeyStore()
    {
        final Runnable saveTrustStore = new Runnable()
        {
            public void run()
            {
                try
                {
                    FileOutputStream foStream = new FileOutputStream( trustKeyStoreFile );
                    synchronized( trustKeyStore )
                    {
                        trustKeyStore.store( foStream, trustKeyStorePassword );
                        System.out.println( "trustStore saved" );
                    }
                }
                catch( Exception e )
                {
                    e.printStackTrace();
                }
            }
        };
        final ScheduledFuture<?> storeHandle = scheduler.scheduleAtFixedRate( saveTrustStore, Info.SAVE_DELAY, Info.SAVE_INTERVAL,
            java.util.concurrent.TimeUnit.SECONDS );
        scheduler.schedule( new Runnable()
        {
            public void run()
            {
                storeHandle.cancel( true );
            }
        }, Info.UNTIL_END_OF_REGISTRATION_SECONDS, java.util.concurrent.TimeUnit.SECONDS );
    }

    public void listenSocket()
    {
        try
        {
            storeTrustKeyStore();
            ExecutorService pool = Executors.newCachedThreadPool();
            SSLServerSocket sslSocket = SSLSocketHandler.createServerSocket( Info.REGISTRATION_PORT, trustKeyStore, keyStore,
                keyStorePassword );
            System.out.println( "RegistrationServer: listening on port " + Info.REGISTRATION_PORT );
            sslSocket.setNeedClientAuth( false );
            while( isRunning )
            {
                Callable<Certificate> rw;
                try
                {
                    rw = new RegistrationWorker( sslSocket.accept() );
                    Future<Certificate> result = pool.submit( rw );
                    // pytanie do kogos kto sie zna -> jak to w ogole dziala?
                    // czy jak watki sie koncza i cos
                    // zwracaja to wywolujacy je watek jakos kolejkuje
                    // odpowiedzi i potem je przetwarza?
                    Certificate cert = result.get();
                    if( cert == null )
                    {
                        System.out.println( "Server: dane sie nie zgadzaja" );
                    }
                    else
                    {
                        String dnString = ( (X509Certificate)cert ).getSubjectDN().toString();
                        System.out.println( "Server: new trusted cert -> " + dnString + " by "
                                + ( (X509Certificate)cert ).getIssuerDN().toString() );
                        // pesel as an alias in trustKeyStore
                        String pesel = dnString.substring( dnString.indexOf( "OU=" ) + 3 );
                        System.out.println( "Server: pesel -> " + pesel );
                        KeyStore.Entry entry = new KeyStore.TrustedCertificateEntry( cert );
                        trustKeyStore.setEntry( pesel, entry, null );
                        // nie bedziemy tutaj zapisywac, za to zrobie
                        // zapisywanie automatyczne co 15s
                        // FileOutputStream fos = new
                        // FileOutputStream(trustKeyStoreFile);
                        // try{
                        // trustKeyStore.store(fos, trustKeyStorePassword);
                        // } catch (Exception e){
                        // e.printStackTrace();
                        // } finally {
                        // fos.close();
                        // }
                        // trustKeyStore.setCertificateEntry(pesel, cert);
                    }
                }
                catch( IOException e )
                {
                    e.printStackTrace();
                }
                catch( InterruptedException e )
                {
                    e.printStackTrace();
                }
                catch( ExecutionException e )
                {
                    e.printStackTrace();
                }
            }
        }
        catch( KeyStoreException e )
        {
            e.printStackTrace();
            System.exit( 1 );
        }
        catch( UnrecoverableKeyException e )
        {
            e.printStackTrace();
            System.exit( 1 );
        }
        catch( IOException e )
        {
            e.printStackTrace();
            System.exit( 1 );
        }
    }
}
